package YAPserver.logic;

import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import YAPenvironment.YAPconstant;
import YAPenvironment.YAPconstant.SideState;
import YAPserver.behaviours.CollideWall;
import YAPserver.behaviours.CollideBehaviour;
import YAPserver.behaviours.CollideSlider;
import YAPserver.gameObjects.AbstractBall;
import YAPserver.gameObjects.Mod;
import YAPserver.gameObjects.Slider;
import YAPserver.gameObjects.GameWall;
import YAPserver.logic.Input.MoveCommand;
import YAPserver.score.Award;

public class Player extends Observable implements Observer{
	private String m_name;
	private int m_inGameId;
	public CollideBehaviour m_collideWall;
	public CollideBehaviour m_collideSlider;
	private Slider m_slider;
	private GameWall m_wall;
	private MoveCommand m_move;
	private boolean topsyTurvy;
	//private String m_name; //probably not necessary
	private int m_lives;
	private ArrayList<Award> awards = new ArrayList<Award>();
	private Output m_out;
	
	/**
	 * Constructs a player with an id. Its name is also its id and has an empty (unlinked) output.
	 * That means nothing from this player will be sent to the client.
	 * @param inGameId
	 */
	public Player(int inGameId){
		m_inGameId = inGameId;
		m_lives = YAPconstant.C_LIVES;
		m_collideWall = new CollideWall();
		m_collideSlider = new CollideSlider();
		m_name = ""+inGameId; //default setting
		m_out = new Output();
	}
	
	/**
	 * Constructs a player with a name an id and links to the output of the game.
	 * @param inGameId
	 * @param name
	 * @param out
	 */
	public Player(int inGameId, String name, Output out){
		m_inGameId = inGameId;
		m_lives = YAPconstant.C_LIVES;
		m_collideWall = new CollideWall();
		m_collideSlider = new CollideSlider();
		m_name = name;
		m_out = out;
	}
	
	/**
	 * Performs a collision with this wall. Note this does not involve bouncing the ball. Rather the reaction of this player when there is such a collision.
	 * @param ball ballAbstract that collided with the wall
	 */
	public void performWallCollision(AbstractBall ball){
		m_collideWall.collide(this, ball, m_out);
	}
	
	/**
	 * Performs a collision with this slider. Note this does not involve bouncing the ball. Rather the reaction of this player when there is such a collision.
	 * @param ball ballAbstract that collided with the wall
	 */
	public void performSliderCollision(AbstractBall ball){
		m_collideSlider.collide(this, ball, m_out);
	}
	
	/**
	 * Sets the behaviour of the player when there is a collision between a ballAbstract and this player's wall.
	 * @param col new CollideBehaviour of the player
	 */
	public void setWallBehaviour(CollideBehaviour col){
		m_collideWall = col;
	}
	
	/**
	 * Sets the behaviour of the player when there is a collision between a ballAbstract and this player's slider.
	 * @param col new CollideBehaviour of the player
	 */
	public void setSliderBehaviour(CollideBehaviour col){
		m_collideSlider = col;
	}
	
	/**
	 * Sets the player's slider and wall and sets the player as observer of both.
	 * @param wall wall to be set as this player's
	 * @param slider slider to be set as this player's
	 */
	public void setWallAndSlider(GameWall wall, Slider slider){
		m_wall = wall;
		m_slider = slider;
		m_wall.addObserver(this);
		m_slider.addObserver(this);
		m_wall.setColour(SideState.none);
	}
	
	/**
	 * Stops this player from observing anything it was previously observing.
	 */
	public void stopObserving(){
		m_wall.deleteObserver(this);
		m_wall.setColour(SideState.wall);
		m_slider.deleteObserver(this);
	}

	@Override
	public void update(Observable obs, Object ball) {
		/* the update is only called when a ball hits the player's wall
		 * or if a Mod hits the player's slider
		 */
		if(ball instanceof AbstractBall && obs instanceof GameWall){
			performWallCollision((AbstractBall) ball);
			return;
		}
		if(ball instanceof AbstractBall && obs instanceof Slider){
			performSliderCollision((AbstractBall) ball);
			return;
		}
		if(ball instanceof Mod && obs instanceof Mod){
			((Mod) ball).deactivate(this);
			return;
		}
	}
	
	/**
	 * Moves the player's slider left, right or not at all depending on what it's moveCommand is set to.
	 * @see MoveCommand
	 * @see Input
	 */
	public void move(){
		if(topsyTurvy){
			switch(m_move){
			case LEFT:
				this.m_slider.moveRight();
				return;
			case RIGHT:
				this.m_slider.moveLeft();
				return;
			default:
				return;
			}
		}else{
			switch(m_move){
			case LEFT:
				this.m_slider.moveLeft();
				return;
			case RIGHT:
				this.m_slider.moveRight();
				return;
			default:
				return;
			}
		}
	}
	
	/**
	 * Adds a life to this player and notifies the output.
	 */
	public void addLife(){
		this.m_lives++;
		this.setChanged();
		this.notifyObservers(this.toString());
	}
		
	/**
	 * Subtracts a life from this player and notifies the output.
	 */
	public void subtractLife(){
		this.m_lives = this.m_lives - 1;
		this.setChanged();
		this.notifyObservers();
	}
	
	/**
	 * Returns the number of lives this player has.
	 * @return the number of lives this player has
	 */
	public int getLives(){
		return this.m_lives;
	}

	/**
	 * Returns this player's wall.
	 * @return this player's wall.
	 */
	public GameWall getWall() {
		return this.m_wall;
	}
	
	/**
	 * Sets this player's moveCommand
	 * @param move moveCommand to be set
	 * @see MoveCommand
	 * @see Input
	 */
	public void setMoveCommand(MoveCommand move){
		this.m_move = move;
	}

	/**
	 * Returns this player's id.
	 * @return this player's id
	 */
	public Integer getId() {
		return this.m_inGameId;
	}
	
	/**
	 * Returns this player's slider.
	 * @return this player's slider
	 */
	public Slider getSlider() {
		return m_slider;
	}

	/**
	 * Sets how many lives this player will have.
	 * @param lives number of lives to be set
	 */
	public void setLives(int lives) {
		m_lives = lives;
//		this.setChanged();
//		this.notifyObservers();
	}
	
	/**
	 * Toggles whether or not the player will play topsy-turvy. That means if he presses right he'll go left.
	 */
	public void toggleTopsyTurvy(){
		if(this.topsyTurvy){
			this.topsyTurvy = false;
		}else{
			this.topsyTurvy = true;
		}
	}
	
	/**
	 * Adds to this player's individual score that may lead to an award.
	 * @param award award towards which will be scored
	 */
	public void score(Award award){
//		award.add(m_LobbyId);
		award.add(this);
	}
	
	/**
	 * Returns this player's score of the specified award.
	 * @param award award towards which was scored
	 * @return this player's score of the specified award
	 */
	public int getScore(Award award){
//		return award.getScore(m_LobbyId);
		return award.getScore(this);
	}
	
	/**
	 * Returns this player's name.
	 * @return this player's name
	 */
	public String getName(){
		return m_name;
	}

	/**
	 * Gives the player an award which is added to its list of awards.
	 * @param award award to be added
	 */
	public void giveAward(Award award) {
		awards.add(award);
	}
	
	/**
	 * Returns the list of awards.
	 * @return the list of awards
	 */
	public ArrayList<Award> getAward(){
		return awards;
	}

	@Override
	public String toString(){
		String s = "";
		s = s + m_inGameId + YAPconstant.C_SEPARATOR + m_lives;
		return s;
	}
}

